package xyz.heyaoshare.annotation;

import cn.hutool.core.date.LocalDateTimeUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.web.multipart.MultipartFile;
import xyz.heyaoshare.config.properties.LogProperties;
import xyz.heyaoshare.config.satoken.context.UserContextHolder;
import xyz.heyaoshare.core.entity.system.Logs;
import xyz.heyaoshare.core.entity.system.User;
import xyz.heyaoshare.core.service.system.LogsService;
import xyz.heyaoshare.utils.HttpUtils;
import xyz.heyaoshare.utils.JSONUtils;
import xyz.heyaoshare.utils.StrUtils;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.List;

/**
 * 日志操作和输出 切面
 * @author YueHe
 * @version 0.0.1
 * @since 2025/6/25 上午10:17
 */
@Data
@Slf4j
@Aspect
@Component
public class OperationLogAspect {

    @Resource
    private LogProperties logProperties;
    @Resource
    private LogsService logsService;

    private String requestURI;
    private String requestMethod;
    private String requestParams;
    private String summary;
    private String tagName;
    private Long startTime;
    private String operateMethod;

    @Before("@annotation(operationLog)")
    public void logRequestParams(JoinPoint joinPoint, OperationLog operationLog) {
        // 记录请求开始时间
        this.startTime = System.currentTimeMillis();
        // 获取当前请求对象
        HttpServletRequest request = HttpUtils.getRequest();
        // 获取当前执行的方法所在的类
        Class<?> clazz = joinPoint.getTarget().getClass();
        // 获取当前执行的方法对象
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        // 获取方法参数
        Object[] args = joinPoint.getArgs();
        // 获取当前请求的URI
        this.requestURI = request.getRequestURI();
        // 获取请求参数
        this.requestParams = handlerArgs(args);
        // 获取请求方法
        this.requestMethod = request.getMethod();
        // 获取请求方法的注释
        this.summary = getSummary(operationLog, method);
        // 获取请求方法所在的类名
        this.tagName = getTagName(clazz);
        // 获取请求方法所在的类和方法名
        this.operateMethod = getOperateMethod(clazz, method);
        // 打印请求参数信息
        if (logProperties.getPrintLog()) {
            log.info("""
                            \n--------------------- request ---------------------
                            Name: {}
                            Url: {}
                            Method：{}
                            Params: {}
                            OutTime: {}
                            --------------------- end ---------------------""",
                    // 如果summary不为空，则使用summary，否则使用"无"
                    this.summary,
                    // 请求的URI
                    this.requestURI,
                    // 请求方法
                    this.requestMethod,
                    // 请求参数
                    this.requestParams,
                    // 输出时间
                    LocalDateTimeUtil.format(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss"));
        }
    }


    @AfterReturning(pointcut = "@annotation(operationLog)", returning = "response")
    public void logResponse(JoinPoint joinPoint, OperationLog operationLog, Object response) {
        if (logProperties.getPrintLog()) {
            // 记录结束时间
            long endTime = System.currentTimeMillis();
            // 如果响应不为空，则记录日志
            log.info("""
                            \n--------------------- response ---------------------
                            Name: {}
                            Url: {}
                            Method：{}
                            Response: {}
                            TakeTime: {}ms
                            OutTime: {}
                            --------------------- end ---------------------""",
                    // 如果summary不为空，则使用summary，否则使用"无"
                    this.summary,
                    // 请求的URI
                    this.requestURI,
                    // 请求方法
                    this.requestMethod,
                    // 响应的内容
                    handlerResponse(response),
                    // 计算请求耗时
                    endTime - this.startTime,
                    // 输出时间
                    LocalDateTimeUtil.format(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss"));
        }
    }


    @Around("@annotation(operationLog)")
    public Object logAround(ProceedingJoinPoint joinPoint, OperationLog operationLog) throws Throwable {
        HttpServletRequest request = HttpUtils.getRequest();
        String requestURI = request.getRequestURI();
        String requestMethod = request.getMethod();
        if (requestURI.startsWith("/v3/api-docs") || requestURI.startsWith("/swagger-ui") || requestURI.startsWith("/webjars")) {
            return joinPoint.proceed();
        }

        Object result = null;
        boolean success = true;
        Class<?> clazz = joinPoint.getTarget().getClass();
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        StopWatch stopWatch = new StopWatch();

        try {
            stopWatch.start();
            result = joinPoint.proceed();
        } catch (Exception e) {
            success = false;
            throw e;
        } finally {
            saveLog(joinPoint, clazz, method, result, operationLog, success, stopWatch);
        }

        return result;
    }


    /**
     * 保存日志信息
     *
     * @param joinPoint 切面对象
     * @param clazz     类对象
     * @param method    方法对象
     * @param result    方法返回值
     * @param operationLog 操作日志对象
     * @param success   操作是否成功
     * @param stopWatch 计时器对象
     */
    private void saveLog(ProceedingJoinPoint joinPoint, Class<?> clazz, Method method, Object result, OperationLog operationLog, boolean success, StopWatch stopWatch) {
        stopWatch.stop();
        User user = UserContextHolder.get();
        if (logProperties.getSaveLog() && user != null) {
            Logs logs = new Logs().setModule(getTagName(clazz)).setOperation(getSummary(operationLog, method)).setOperator(user.getUsername()).setTaskTime(getTaskTime(stopWatch)).setOperationStatus(getOperationStatus(success)).setRequestUrl(requestURI).setRequestMethod(requestMethod).setOperateMethod(getOperateMethod(clazz, method)).setRequestParams(handlerArgs(joinPoint.getArgs())).setResponseData(JSONUtils.toJsonString(result));
            logs.setCreateTime(LocalDateTime.now()).setCreator(user.getId().toString()).setUpdater(user.getId().toString()).setUpdateTime(LocalDateTime.now());
            logs.setDeleted(Boolean.FALSE).setVersion(0L);
            logsService.saveLogs(logs, Boolean.TRUE);
        }
    }

    /**
     * 根据操作是否成功返回相应的状态字符串。
     *
     * @param success 表示操作是否成功的布尔值，true表示成功，false表示失败
     * @return 返回表示操作状态的字符串，成功返回"success"，失败返回"failed"
     */
    private String getOperationStatus(boolean success) {
        return success ? "success" : "failed";
    }

    /**
     * 获取任务执行时间（毫秒）
     *
     * @param stopWatch StopWatch 对象，用于记录任务执行时间
     * @return 任务执行时间（毫秒），格式为"时间（毫秒）"
     */
    private String getTaskTime(StopWatch stopWatch) {
        return stopWatch.getTotalTimeMillis() + "ms";
    }

    /**
     * 获取指定类的Tag注解中的name属性值
     *
     * @param clazz 需要获取Tag注解的类
     * @return 如果clazz上有Tag注解，则返回Tag注解中的name属性值；否则返回"未知"
     */
    private String getTagName(Class<?> clazz) {
        Tag tag = clazz.getAnnotation(Tag.class);
        if (tag != null) {
            return tag.name();
        }
        return "未知";
    }

    /**
     * 获取操作方法的描述字符串
     *
     * @param clazz 类对象
     * @param method 方法对象
     * @return 描述字符串，格式为"类名/方法名"
     */
    private String getOperateMethod(Class<?> clazz, Method method) {
        String clazzName = clazz.getName().substring(clazz.getName().lastIndexOf(".") + 1);
        String methodName = method.getName();
        return StrUtils.format("{}/{}", clazzName, methodName);
    }

    /**
     * 获取方法的概要信息
     *
     * @param operationLog  日志打印信息对象
     * @param method    方法对象
     * @return 方法的概要信息
     */
    private String getSummary(OperationLog operationLog, Method method) {
        // 尝试从方法上获取@Operation注解
        Operation operation = method.getAnnotation(Operation.class);
        String summary = StrUtils.isNotBlank(operationLog.value()) ? operationLog.value() : null;
        // 如果存在@Operation注解，则获取其summary值
        if (operation != null && summary == null) summary = operation.summary();
        return summary;
    }

    /**
     * 处理方法参数
     *
     * @param args 方法参数数组
     * @return 处理后的参数JSON字符串
     */
    private String handlerArgs(Object[] args) {
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof MultipartFile) {
                args[i] = ((MultipartFile) args[i]).getOriginalFilename();
            } else if (args[i] instanceof MultipartFile[] files) {
                StringBuilder sb = new StringBuilder();
                for (MultipartFile file : files) {
                    sb.append(file.getOriginalFilename()).append(",");
                }
                args[i] = sb.toString();
            } else if (args[i] instanceof List<?> list) {
                if (!list.isEmpty() && list.getFirst() instanceof MultipartFile) {
                    List<MultipartFile> files = (List<MultipartFile>) list;
                    StringBuilder sb = new StringBuilder();
                    for (MultipartFile file : files) {
                        sb.append(file.getOriginalFilename()).append(",");
                    }
                    args[i] = sb.toString();
                }
            } else if (args[i] instanceof HttpServletResponse || args[i] instanceof HttpServletRequest) {
                args[i] = "";
            }
        }
        return JSONUtils.toJsonString(args);
    }

    /**
     * 处理响应对象。
     *
     * @param response 待处理的响应对象
     * @return 如果响应对象是InputStream类型，则返回字符串"InputStream"；否则返回原响应对象
     */
    private Object handlerResponse(Object response) {
        if (response instanceof InputStream) {
            return "InputStream";
        }
        return response;
    }

}